using System;
using System.Diagnostics;
using System.IO;
using System.Windows;
using OFX.Aggregator.Infrastructure;

namespace OFX.Aggregator.Implementation
{
	public sealed class StatementProcessingWorkflow : IStatementProcessingWorkflow
	{
		private readonly IStatementAction action;
		private readonly IIoCContainerAbstraction container;
		private readonly StatementSource statementSource;

		public StatementProcessingWorkflow(StatementSource statementSource, IStatementAction action,
		                                   IIoCContainerAbstraction container)
		{
			this.container = container;
			this.action = action;
			this.statementSource = statementSource;
		}

		#region Implementation of IStatementProcessingWorkflow

		public void PerformWorkflow()
		{
			if (WorkflowStarted != null)
				WorkflowStarted(this, EventArgs.Empty);

			retrieveStatement();
		}

		public StatementSource StatementSource
		{
			[DebuggerStepThrough]
			get { return this.statementSource; }
		}

		public IStatementAction Action
		{
			[DebuggerStepThrough]
			get { return this.action; }
		}

		public DateTime? DateStart
		{
			[DebuggerStepThrough]
			get { return this.statementSource.LowerCap; }
		}

		public DateTime? DateEnd
		{
			[DebuggerStepThrough]
			get { return null; }
		}

		public event EventHandler WorkflowStarted;
		public event EventHandler WorkflowFinished;

		private void retrieveStatement()
		{
			DateTime dateStart = this.statementSource.LowerCap;
			var asyncStatementRetrieval = this.statementSource.Retrieval as IAsyncStatementRetrieval;
			if (asyncStatementRetrieval != null)
			{
				// Asynchronous retrieval
				asyncStatementRetrieval.BeginGetStatement(this.container, this, onEndRetrieveStatement);
			}
			else
			{
				// Synchronous retrieval
				actOnStatement(delegate { return this.statementSource.Retrieval.GetStatement(dateStart, null); });

				var progressReport = container.Resolve<IProgressReport>();
				progressReport.OnFinish();
			}
		}

		private void onEndRetrieveStatement(IAsyncResult asyncResult)
		{
			actOnStatement(
				delegate { return ((IAsyncStatementRetrieval) this.statementSource.Retrieval).EndGetStatement(asyncResult); });
		}

		private static Exception getInnermostException(Exception ex)
		{
			if (ex != null)
			{
				while (ex.InnerException != null)
					ex = ex.InnerException;
			}

			return ex;
		}

		private void actOnStatement(Func<Statement> retrieveStatementFunc)
		{
			try
			{
				Statement statement = retrieveStatementFunc();

				interpretStatement(statement);
			}
			catch (Exception ex)
			{
				MessageBox.Show(getInnermostException(ex).Message, Application.Current.MainWindow.Title, MessageBoxButton.OK,
				                MessageBoxImage.Stop);
			}
			finally
			{
				if (WorkflowFinished != null)
					WorkflowFinished(this, EventArgs.Empty);
			}
		}

		private void interpretStatement(Statement statement)
		{
			if (statement != null)
			{
				if (this.statementSource.PostProcessor != null)
				{
					this.statementSource.PostProcessor.Process(statement);
				}

				using (Stream statementStream = this.statementSource.Serializer.Serialize(statement))
				{
					using (Stream ofxStream = this.statementSource.OfxConverter.ConvertToOfx(statementStream))
					{
						this.action.DoAction(ofxStream);

						this.statementSource.LowerCap = statement.DateEnd;
						if ((DateTime.Now - statement.DateEnd) < this.statementSource.TransactionListingLag)
						{
							// Allow 24 hours for statement providers that may take that long to make transactions available
							this.statementSource.LowerCap -= new TimeSpan(1, 0, 0, 0);
						}
						this.statementSource.MarkUpdated();
					}
				}
			}
		}

		#endregion
	}
}